package lk.icta.user.service.impl;

import lk.icta.common.AppConstants;
import lk.icta.common.ErrorCodes;
import lk.icta.user.common.*;
import lk.icta.user.dao.RoleDAO;
import lk.icta.user.dao.UserDAO;
import lk.icta.user.entity.User;
import lk.icta.user.service.UserManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;

public class UserManagerImpl implements UserManager {

    private static final Logger logger = LoggerFactory.getLogger(UserManagerImpl.class);

    private final UserDAO userDao;
    private final RoleDAO roleDao;

    public UserManagerImpl(UserDAO userDao, RoleDAO roleDao) {
        this.userDao = userDao;
        this.roleDao = roleDao;
    }

    public User authenticateUser(String userId, String password) throws AuthorizationException {
        //User user = userDao.getUserByPK(userId);
        User user = userDao.getUserByUserName(userId);
        if (user != null && user.getStatus() == User.State.ACTIVE &&
                password != null && user.getPasswordHash() != null) {
            if (user.getPasswordHash().equals(hashPassword(password))) {
                return user;
            }
        }
        logger.warn("Invalid user ID, password or user : " + userId);
        throw new AuthorizationException("Invalid user ID, password or user : " + userId,
                ErrorCodes.INVALID_LOGIN);
    }

    public User getUserByUserName(String userName) {
        return userDao.getUserByUserName(userName);
    }

    public final String hashPassword(String password) {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            logger.warn("Cannot instantiate a SHA-1 message digest", e);
            throw new UserRuntimeException("Cannot instantiate a SHA-1 message digest", e);
        }
        sha.reset();
        sha.update(password.getBytes());
        return new String(Base64.encode(sha.digest()));
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public List<User> getUsersByOrganization(int organizationId, User user) {
        logger.debug("Search organization  users of organization id : {}", organizationId);
        return userDao.getUsersByOrganization(organizationId, user);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public List<User> getORPIIUsersByOrganization(int organizationId, User user) {
        logger.debug("Search ORPII for organization of organization id : {}", organizationId);
        return userDao.getORPIIUsersByOrganization(organizationId, user);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public List<User> getNewURMGOIUsersByOrganization(int organizationId, User user) {
        logger.debug("get new  UR-MGOI User list for organization of organization id :{}", organizationId);
        return userDao.getNewURMGOIUsersByOrganization(organizationId, user);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void activeUser(User user, User adminUser) {
        logger.debug("attempt to active user");
        // does user has authorization to add a new user
        if (!adminUser.isAuthorized(Permission.USER_MANAGEMENT)) {
            logger.debug("not authorized");
            handleELandRuntimeException(adminUser.getUsername() + " doesn't have permission to active a user",
                    ErrorCodes.AUTHORIZATION_FAILS_USER_MANAGEMENT);
        } else {
            logger.debug("authorized");
            user.setStatus(User.State.ACTIVE);
            userDao.updateUser(user);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void inactiveUser(User user, User adminUser) {
        logger.debug("attempt to inactive user");
        // does user has authorization to add a new user
        if (!adminUser.isAuthorized(Permission.USER_MANAGEMENT)) {
            logger.debug("not authorized");
            handleELandRuntimeException(adminUser.getUsername() + " doesn't have permission to inactive a user",
                    ErrorCodes.AUTHORIZATION_FAILS_USER_MANAGEMENT);
        } else {
            logger.debug("authorized");
            user.setStatus(User.State.INACTIVE);
            userDao.updateUser(user);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public List<User> getUsersByRole(String role, User user) {
        logger.debug("");
        return userDao.getUsersByRole(role);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public List<User> getUsersByRoleDS(String role,int dsId, User user) {
        logger.debug("getUsersByRoleDS");
        return userDao.getUsersByRoleDS(role,dsId,user);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void createUser(User userToCreate, User adminUser, String roleId, String randomPassword) {
        logger.debug("attempt to create user");
        // does user has authorization to add a new user
        if (!adminUser.isAuthorized(Permission.USER_MANAGEMENT)) {
            logger.debug("not authorized");
            handleELandRuntimeException(adminUser.getUsername() + " doesn't have permission to create a user",
                    ErrorCodes.AUTHORIZATION_FAILS_USER_MANAGEMENT);
        } else {
            logger.debug("authorized {}", roleId);
            userToCreate.setStatus(User.State.ACTIVE);
            if (AppConstants.URMGOI_USER_ROLE.equals(roleId)) {
                /*UR-MGOI User will be  active by OR-PII*/
                userToCreate.setStatus(User.State.INACTIVE);
            }
            userToCreate.setPrefLanguage(AppConstants.ENGLISH);
            userToCreate.setRole(roleDao.getRole(roleId));
           // userToCreate.setFirstLogin(true);
            userToCreate.setLoginState(User.LoginState.FIRST_LOGIN);
            userToCreate.setPasswordHash(hashPassword(hashPassword(randomPassword)));
            userToCreate.getLifeCycleInfo().setCreatedTimeStamp(new Date());
            userToCreate.getLifeCycleInfo().setCreatedUser(adminUser);
            userToCreate.getLifeCycleInfo().setLastUpdatedTimeStamp(new Date());
            userToCreate.getLifeCycleInfo().setLastUpdatedUser(adminUser);
            User checkDuplicate = userDao.getUserByUserName(userToCreate.getUsername());

            if (checkDuplicate != null) {
                throw new UserRuntimeException("User Name is already assigned" + userToCreate.getUsername(),
                        ErrorCodes.ENTITY_ALREADY_EXIST);
            } else {
                userDao.addUser(userToCreate);
                logger.debug("New user {} created by : {}", userToCreate.getUsername(), adminUser.getUsername());
            }
        }
    }


    @Transactional(propagation = Propagation.REQUIRED)
    public void updatePreferredLanguage(String prefLanguage, User user) {
        user.setPrefLanguage(prefLanguage);
        userDao.updateUser(user);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void createUser(User userToCreate, User adminUser) {
        try {
            logger.debug("attempt to create user");
            // does user has authorization to add a new user
            if (!adminUser.isAuthorized(Permission.USER_MANAGEMENT)) {
                logger.debug("not authorized");
                handleELandRuntimeException(adminUser.getUsername() + " doesn't have permission to create a user",
                        ErrorCodes.AUTHORIZATION_FAILS_USER_MANAGEMENT);
            } else {
                userToCreate.setStatus(User.State.ACTIVE);
                if (AppConstants.URMGOI_USER_ROLE.equals(userToCreate.getRole().getId())) {
                    /*UR-MGOI User will be  active by OR-PII*/
                    userToCreate.setStatus(User.State.INACTIVE);
                }
                userToCreate.setPasswordHash(hashPassword(hashPassword(userToCreate.getPasswordHash())));
                userToCreate.setPrefLanguage(AppConstants.ENGLISH);
              //  userToCreate.setFirstLogin(true);
                userToCreate.setLoginState(User.LoginState.FIRST_LOGIN);
                userToCreate.getLifeCycleInfo().setCreatedTimeStamp(new Date());
                userToCreate.getLifeCycleInfo().setCreatedUser(adminUser);
                userToCreate.getLifeCycleInfo().setLastUpdatedTimeStamp(new Date());
                userToCreate.getLifeCycleInfo().setLastUpdatedUser(adminUser);
                User checkDuplicate = userDao.getUserByUserName(userToCreate.getUsername());
                if (checkDuplicate != null) {
                    throw new UserRuntimeException("User Name is already assigned" + userToCreate.getUsername(),
                            ErrorCodes.ENTITY_ALREADY_EXIST);
                } else {
                    userDao.addUser(userToCreate);
                    logger.debug("New user {} created by : {}", userToCreate.getUsername(), adminUser.getUsername());
                }
            }
        } catch (Exception e) {
            logger.error("Error creating user ", e);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(User userToUpdate, User adminUser) {
        // does user has authorization to add a update user
        if (!adminUser.isAuthorized(Permission.USER_MANAGEMENT)) {

            handleELandRuntimeException(adminUser.getUsername() + " doesn't have permission to update a user",
                    ErrorCodes.AUTHORIZATION_FAILS_USER_MANAGEMENT);
        } else {
            // we will not let anyone update deleted user accounts
            User existing = userDao.getUserByUserName(userToUpdate.getUsername());
            try {
                if (existing.getStatus() == User.State.DELETED) {
                    logger.error("Attempt to modify deleted account : " + existing.getUsername() +
                            " by : " + adminUser.getUsername() + " denied");
                    handleELandRuntimeException("Attempt to modify deleted account : " + existing.getUsername() +
                            " by : " + adminUser.getUsername() + " denied", ErrorCodes.AUTHORIZATION_FAILS_USER_MANAGEMENT);
                }
                userToUpdate.getLifeCycleInfo().setCreatedTimeStamp(existing.getLifeCycleInfo().getCreatedTimeStamp());
                userToUpdate.getLifeCycleInfo().setCreatedUser(existing.getLifeCycleInfo().getCreatedUser());
                userToUpdate.getLifeCycleInfo().setLastUpdatedTimeStamp(new Date());
                userToUpdate.getLifeCycleInfo().setLastUpdatedUser(adminUser);
                userToUpdate.setPrefLanguage(existing.getPrefLanguage());
                if (AppConstants.URMGOI_USER_ROLE.equals(adminUser.getRole().getId())) {
                    /*UR-MGOI User will be  active by OR-PII*/
                    userToUpdate.setStatus(User.State.INACTIVE);
                }
                userDao.updateUser(userToUpdate);
            } catch (Exception e) {
                logger.debug("Error updating user : {}", e);
            }

        }
    }

    public void deleteUser(User userToDelete, User adminUser) {
        userToDelete.setStatus(User.State.DELETED);
        updateUser(userToDelete, adminUser);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updatePassword(String newPass, User user) {
        user.setPasswordHash(hashPassword(newPass));
        userDao.updateUser(user);
    }

    private void handleELandRuntimeException(String message, int code) {
        logger.error(message);
        throw new UserRuntimeException(message, code);
    }

    public List<User> getAllUsers() {
        return userDao.getAllUsers();
    }
}
